פתרון תרגיל 4 שאלה 1: הציעו דרך לממש תור )FIFO( באמצעות ערימה, תשובתכם צריכה לכלול פסאודו-קוד, המתאר את הפעולות הבאות: isempty(),enqueue(x), dequeue(), בנוסף נתחו זמן ריצה במקרה הגרוע ביותר עבור כ"א מהפעולות הנ"ל. תיאור המבנה: משתנה בשם counter המאותחל ל- 0. ערימת מינימום H, בה איברים מאוחסנים לפי ערך ה- counter בעת הכנסתם. אחרי כל הכנסה counter גדל ב- 1. כלומר לכל איבר חדש שמכניסים למבנה מוסיפים שדה key השווה לערך הנוכחי של ה- counter. זמן ריצה (1)O. זמן ריצה (n,o(log כאשר n מספר איברים במבנה. זמן ריצה (n,o(log כאשר n מספר איברים במבנה. טעויות נפוצות: isempty() return H.size == 0 enqueue(x) HeapNode newnode = (x, counter) counter = counter +1 H.insert(newNode) dequeue(x) return H.extractMin().value מימוש וריאציות של שיטות מה ADT ערימה, כגון Heapify, ExtractMin במקום להשתמש בהן.as is לא מוסיפים מפתח לאיבר בעת הכנסה. הכנסת איבר חדש בצורה ישירה לסוף המערך )מבלי להצמיד לו מפתח(- חוץ מזה שיש כאן הפרה של הממשק ערימה )יש להשתמש ב,)insert עלולה להיווצר בעיה בעת מחיקת איבר מהערימה וביצוע.heapify עדכון counter->counter-1 בפעולת הוצאה. מפר סדר FIFO אם מבצעים מספר הוצאות ואז הכנסה, בנוסף עלולים לקבל כפילויות של מפתחות. שימוש ב heap-size בתור המפתח- גם כאן עלולה להיווצר אותה הבעיה כמו בסעיף הקודם.
שאלה 2: בישוב "סנסנה" נפתחה פיצרייה חדשה. בישוב כביש יחיד וישר כאשר כל בתי היישוב נמצאים לאורכו. הפיצרייה נפתחה בקצה הכביש. הפיצרייה מבצעת משלוחים לבתי הלקוחות. נניח שכל הזמנה שמתקבלת מכילה את הנתונים הבאים: זמן ההזמנה, כמות הפיצות, סוג כל פיצה ומרחק היעד מהפיצרייה כאשר המרחקים הם מספרים שלמים חיוביים. הפיצרייה מעוניינת לנהל את שירות המשלוחים שלה בצורה היעילה ביותר. לכן מנהלי הפיצרייה החליטו על החוקים הבאים בהוצאת משלוחים: אם קיימות הזמנות שמחכות בתור כבר חצי שעה או יותר אז יש להוציא משלוח בהול רק עבור ההזמנה הישנה ביותר. אחרת, ההזמנה הבאה שתצא תהיה ליעד הקרוב ביותר לפיצרייה. יחד איתה במשלוח יצאו כל ההזמנות במרחק 3 ממנה )מרחקים הם מספרים שלמים חיוביים שונים זה מזה(. הציעו מבנה נתונים בגודל O(n) עבור ניהול המשלוחים בזמנים הבאים:.O(n) הזמנות בזמן n אתחול: אתחול המבנה בתחילת יום עם פרטי המשלוח הבא: פירוט כמות וסוג הפיצות שיצאו במשלוח הבא )ליעד אחד או יותר(, ללא הוצאה מהמבנה, בזמן (1)O. ניתן להניח כי לאחר פעולה זו לא מתבצעת הכנסה אלא רק הוצאה..O(logn) הוצאה: הוצאת המשלוח הבא )ליעד אחד או יותר( ע"פ החוקים שנקבעו בזמן הכנסה: הכנסת הזמנה חדשה למבנה בזמן O(logn) כאשר n הוא מספר ההזמנות במבנה. ניתן להניח כי כל ההזמנות המתקבלות הן ליעדים שונים, כלומר מרחק היעד של כל הזמנה מהפיצרייה הוא שונה. תארו את מבנה הנתונים, הסבירו כיצד תבצעו כל פעולה ונתחו את זמני הריצה. נחזיק שתי ערמות מינימום. ערמה ראשונה לפי זמן ההגעה של ההזמנה, והערמה השנייה ע"פ המרחק. כל הזמנה שמתווספת למבנה הנתונים, תוכנס לשתי הערמות. כמו כן נחזיק מצביעים הדדיים בין הערמות. כל איבר יצביע לעותק שלו בערמה השנייה. אתחול בניית שתי הערמות, הכנסת n איברים לשתי הערמות ועדכון המצביעים-.O(n) הכנסת הזמנה הוספת ההזמנה לשתי הערמות ועדכון המצביעים בין שני עותקי ההזמנה- O(logn) הוצאת הזמנה נבדוק את ראש הערמה הממוינת לפי זמן. אם מזמן ההזמנה עברו 30 דקות או יותר, נוציא את ההזמנה מראש הערמה )ואת העותק מהערמה השנייה(. אחרת נבצע מחיקה של האיבר בראש ערמת המרחקים,)ExtarctMin( ונמשיך להוציא שבראש הערמה ימצא איבר שרחוק יותר מ 3 יחידות מהאיבר הראשון שהוצאנו )כמובן שכל איבר שיוצא מערמת המרחק יוצא מערמת הזמן בעזרת המצביע(. כיוון ש- 3 קבוע והמרחקים שלמים, סה"כ זמן ההוצאה.O(3logn)=O(logn)
פרטי משלוח הבא נבדוק את ראש הערמה הממוינת לפי זמן. אם מזמן ההזמנה עברו 30 דקות או יותר, נחזיר את ההזמנה. אחרת ניצור רשימה להחזרה באופן הבא: נוסיף לרשימה את ראש ערמת המרחקים, ונסרוק את הערמה באופן הבא- לכל איבר ברשימה )שתגדל באופן טיפוסי אחרי כל איטרציה(, נבדוק את שני בניו. כל בן שמרחקו מהאיבר הראשון שהכנסנו קטן או שווה 3 יתווסף לרשימה. במקרה הגרוע נכניס לרשימה 4 איברים ונסרוק גם את שני הבנים של כל איבר ברשימה. סה"כ (1)O. טעויות נפוצות אין ציון מפורש של קיום מצביעים הדדיים בין שתי הערימות- הכרחי כדי לעמוד בזמן ריצה O(logn) עבור פעולת ההוצאה. שימוש בערימת מקס' עבור זמני המתנה- דורש עדכון של כל איברי הערימה בכל "טיק" של השעון. פירוט של ביצוע heapify או פירוט מימוש פנימי של ערימה- מיותר ומסורבל. יש להשתמש בממשק ערימה מבלי להיכנס לפרטי המימוש. שאלה 3: f(k) = k mod m נניח כי נתונה לנו טבלת גיבוב בגודל 13=m. א. ציירו את טבלת הגיבוב לאחר הכנסת המפתחות הבאים )משמאל לימין(: 16,7,32,4,9,19,45,39,21,3 הטבלה מנוהלת בשיטת Chaining עם פונקציית הגיבוב הבאה: addressing) (open עם גיבוב כפול נתונה טבלת הגיבוב הבאה המנוהלת בשיטת מיעון פתוח.(double hashing) 0 1 2 3 4 5 6 7 8 9 39 75 35 42 23 45 ב. המפתחות הוכנסו לטבלה ריקה מלכתחילה תוך שימוש בפונקצית הגיבוב הבאה: h(k, i) = (h 1 (k) + i*h 2 (k)) mod 10 h 1 (k) = k mod 10 h 2 (k) = k/10 mod 10 כאשר: רשמו )משמאל לימין( את סדר הכנסת המפתחות.
א( ב( סדר ההכנסה: 35,45,39,42,75,23 0 39 1 2 3 4 5 6 7 8 9 10 11 12 3 16 4 45 19 32 7 21 9 שאלה 4: א. במיון מניה הלולאה האחרונה רצה מ- N ל- 1: for jn to 1 B[C[A[j]]]A[j] C[A[j]]C[A[j]]-1 במידה ונשנה את זה את הסדר בו רץ המונה למ 1 עד N, האם האלגוריתם עדיין ימיין את המערך נכון? אם לא, הסבירו מדוע, אחרת, הסבירו האם עדיפה הריצה המקורית או הריצה המוצעת בסעיף זה. המערך תוצאה אכן יהיה ממוין, אך תכונת היציבות )stability( לא תשמר. ישנם מקרים בהם תכונה זאת חשובה )כמו למשל ב,)radix sort ולכן נעדיף את הריצה בסדר המקורי. a i, a j כך ש- ב. נתונים n מספרים a 1, a 2,, a n ממשיים בתחום.[0,2k] ידוע כי לכל זוג מספרים a i a j k מתקיים: i j n תארו אלגוריתם הממיין את המספרים במערך בזמן ריצה O(n) במקרה הגרוע ביותר. שימו לב: k יכול להיות גדול בהרבה מ- n, ולכן זמן ריצה, למשל, (k O(n + אינו מספיק טוב. נבצע מיון דלי. כלומר, האלגוריתם מבצע חלוקה של התחום [0,2k] ל- n דליים, מחלק את n המספרים לדליים, ממיין כל דלי בנפרד ומשרשר את המספרים הממוינים. 2n נשים לב שגודל כל דלי הוא ולכן ע"פ הנתון לכל דלי נכנסים לכל היותר 2 איברים, ולכן מיון כל k אחד מהדליים יתבצע במקרה הגרוע בזמן (1)O. אם מוסיפים לכך את החלוקה לדליים והשרשור נקבל שזמן ריצת האלגוריתם במקרה הגרוע הוא.O(n)
שאלה 5: א. חציון של קבוצת מספרים הוא האיבר שחצי מהאברים גדולים ממנו וחצי מהאיברים קטנים ממנו. נניח ונתון אלגוריתם findmed(a) למציאת חציון של מערך A המכיל n מספרים בזמן O(n) במקרה הגרוע. הפונקציה findmed(a) מחזירה את האינדקס של החציון ב A. 1. הציעו מימוש )פסאודו-קוד או תיאור האלגוריתם במילים( ל partition המשתמש באלגוריתם ה -.findmed(a) 2. מה הוא זמן ריצת אלגוריתם partition שהצעתם בסעיף הקודם )סעיף 1( במקרה הגרוע? 3. מה יהיה זמן ריצת quick sort המשתמש ב partition שהצעתם למיון n איברים במקרה הממוצע? 4. מה יהיה זמן ריצת quick sort המשתמש ב partition הנ"ל למיון n איברים במקרה הכי גרוע? Partition(A,p,r) x findmed(a[p r]) i p-1 j r+1 while(true){ repeat j j-1 until A[j] x } repeat i i+1 until A[i] x if i < j swap(a[i],a[j]) else return(j) 2. Θ(n) בדומה ל partition המקורי. findmed לא מגדילה את הזמן הריצה האסימפטוטי..3 n) Θ(n log בדומה לניתוח של quicksort המקורי. logn גם כן. מכיוון שהציר שנבחר בכל שלב מחלק את המערך בדיוק בחצי, לאחר Θ(n log (n 4. שלבים נגיע למערכים בגודל (1)O. ב. מוצע לשכלל את bucket sort למיון רקורסיבי כדלהלן: מצא את תחום ערכי המפתחות, חלק את התחום ל - n חלקים שווים, פזר מפתחות ובמידה ויש bucket בו יותר מ - k מפתחות )k ניתן כפרמטר ולא ניתן להניח כי הוא קבוע(, חלק את תחום ה - bucket הזה למספר המפתחות ב - bucket הזה בצורה רקורסיבית. מהו זמן המיון המקסימאלי בהנחה שכל המפתחות שונים זה מזה? מהו זמן המיון הממוצע בהנחה שכל המפתחות שונים זה מזה?.2 זמן המיון במקרה הגרוע הוא ) 2,Θ(n וזה במקרה בו גודל הקלט קטן ב- 1 בכל קריאה רקורסיבית. כלומר, בכל שלב 1 n k = נכנסים 1 k מפתחות לדלי הראשון ומפתח אחד לאחרון )או הפוך(. במקרה הממוצע, תחת הנחת פילוג אחיד, ייקח Θ(n) בדומה ל sort bucket רגיל..2
שאלה 6: יהי נתון גרף מכוון G המיוצג באמצעות רשימות סמיכויות )שכנויות(. להלן הצעה לאלגוריתם שבודק האם בגרף G קיים מעגל: כל עוד יש ב - G קודקוד v שדרגת היציאה שלו היא 0 )קודקוד זה כמובן לא יכול להיות 1. שייך לשום מעגל ב G(: v. את כל הקשתות הנכנסות ל- G הסר מ a..v את G הסר מ.b אם הגרף שמתקבל הוא גרף ריק )ללא קודקודים( החזר "הגרף חסר מעגלים" אחרת 2. החזר "יש מעגל בגרף". )כלומר, אם אתם חושבים שהאלגוריתם נכון הסבירו במשפט או א. האם האלגוריתם נכון? נמקו שתיים מדוע במקרה שהגרף המתקבל בסוף ביצוע האלגוריתם אינו ריק הוא חייב להכיל מעגל, אחרת אם אתם חושבים שהאלגוריתם לא נכון תנו דוגמה סותרת(. בכל מקרה, בין אם האלגוריתם נכון ובין אם לאו, תארו מימוש שלו הרץ בזמן ( E.O( V + ב. תארו קודם את מבני העזר שברצונכם ליצור וכיצד תאתחלו אותם, ורק לאחר מכן תארו את המימוש שאתם מציעים. הכוונה: על מבני העזר לאפשר שתי פעולות: בחירה של קודקוד בעל דרגת יציאה 0 בזמן ריצה של in כאשר,O(in deg ((v) בזמן ריצה v ומחיקת כל הקשתות הנכנסות לקודקוד נתון (1)O v. היא דרגת הכניסה של deg (v) סעיף א' האלגוריתם נכון. נתחיל טיול מקודקוד כלשהו בגרף הלא ריק שנותר לאחר הרצת האלגוריתם. כיוון שדרגת היציאה של כל הקודקודים שונה מאפס, מכל קודקוד שנגיע )כולל הקודקוד ממנו התחלנו( נוכל לעבור לקודקוד נוסף וליצור מסלול ארוך כרצוננו. אבל מספר הקודקודים בגרף סופי, לכן סופו של דבר שנהיה חייבים לחזור לאותו קודקוד שוב. כלומר הגרף הלא ריק מכיל מעגל. קל וחומר הגרף המקורי. סעיף ב' תיאור מבנה העזר לבחירת קודקוד עם דרגת יציאה 0 בזמן ריצה של (1)O ודרך אתחולו: נרוץ על רשימות השכנים ונאסוף את הקודקודים שרשימת השכנים שלהם ריקה לרשימה חדשה, Z, קודקודים שדרגת היציאה שלהם היא 0. זמן יצירה.O( V ) של תיאור מבנה העזר למחיקת כל הקשתות הנכנסות אל קודקוד נתון v בזמן ((v) O(in deg ואופן אתחולו: נעבור על רשימות השכנים של הגרף הנתון G וניצור מערך B של רשימות ובו כניסה לכל קודקוד i בגרף המקורי. ברשימה שבכניסה B[i] נכניס את קבוצת הקשתות הנכנסות לקודקוד i בגרף המקורי G. חוליה ברשימות האלה תצביע על החוליה הרלוונטית ברשימת השכנויות המקורית )שמייצגת את הגרף(. זמן יצירה.O( V + E ) תיאור האלגוריתם: נסמן ב A את מערך השכנויות שמייצג את הגרף הנתון. ניצור את שני המבנים שלעיל. כל זמן ש- Z אינה ריקה נשלוף קודקוד i מראש הרשימה מ- Z. a. נרוץ על הרשימה B[i] ונמחק את כל הקודקודים )ברשימות השכנים במערך A( אליהם מצביעות חוליות הרשימה.B[i].2.3
b. בכל מקרה בו כניסה במערך A הופכת ל-,null כתוצאה ממחיקת החוליה האחרונה שלה, נוסיף את האינדקס של הכניסה המתאפסת לרשימה Z. 4. כשנסיים נחזיר "אין מעגל" אם ורק אם כל הכניסות במערך A שוות ל-.null ניתוח זמן ריצה: שלב 3 מבצע לכל היותר O( V ) איטרציות. איטרציה יחידה יכולה לעלות,O( V ) אבל אם מסתכלים על כל האיטרציות יחדיו, בסה"כ נראה שכל קשת נבחנת בדיוק פעם אחת. לכן סה"כ מקבלים זמן ריצה של ( E.O( V +